package com.huaxonline.boot.web.sign;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.TreeMap;

/**
 * 签名SDK客户端
 * @author zhaoshulei
 * @since 2023/9/13
 */
@Slf4j
@Data
public class SignClient {
    
    /**
     * 应用ID
     */
    private String appId;
    
    /**
     * 应用密钥
     */
    private String secret;
    
    /**
     * 签名算法
     */
    private SignAlgorithm algorithm = SignAlgorithm.SHA256;
    
    /**
     * 时间戳容差（毫秒）
     */
    private long timestampDisparity = 900000L;
    
    /**
     * 是否启用调试模式
     */
    private boolean debugMode = false;
    
    public SignClient(String appId, String secret) {
        this.appId = appId;
        this.secret = secret;
    }
    
    public SignClient(String appId, String secret, SignAlgorithm algorithm) {
        this.appId = appId;
        this.secret = secret;
        this.algorithm = algorithm;
    }
    
    /**
     * 生成签名参数
     */
    public SignParams generateSignParams() {
        long timestamp = System.currentTimeMillis();
        String nonce = IdUtil.fastSimpleUUID();
        return new SignParams(appId, timestamp, nonce);
    }
    
    /**
     * 生成签名
     */
    public String generateSign(Map<String, Object> params) {
        // 添加签名基础参数
        Map<String, Object> signParams = new TreeMap<>(params);
        signParams.put(SignConstants.HEADER_APP_ID, appId);
        signParams.put(SignConstants.HEADER_TIMESTAMP, params.get(SignConstants.HEADER_TIMESTAMP));
        signParams.put(SignConstants.HEADER_NONCE, params.get(SignConstants.HEADER_NONCE));
        
        // 构建参数字符串
        StringBuilder sb = new StringBuilder();
        sb.append(SignConstants.HEADER_SECRET).append("=").append(secret).append("&");
        
        for (Map.Entry<String, Object> entry : signParams.entrySet()) {
            if (entry.getValue() != null && StrUtil.isNotBlank(entry.getValue().toString())) {
                sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            }
        }
        
        if (sb.length() > 0) {
            sb.deleteCharAt(sb.length() - 1);
        }
        
        String signStr = sb.toString();
        if (debugMode) {
            log.info("签名字符串: {}", signStr);
        }
        
        return algorithm.sign(signStr, secret);
    }
    
    /**
     * 生成签名（包含请求体）
     */
    public String generateSignWithBody(Map<String, Object> params, String body) {
        Map<String, Object> signParams = new TreeMap<>(params);
        
        // 如果有请求体，计算其哈希值
        if (StrUtil.isNotBlank(body)) {
            String bodyHash = algorithm.sign(body, "");
            signParams.put(SignConstants.HEADER_HASHED_REQUEST_PAYLOAD, bodyHash);
        }
        
        return generateSign(signParams);
    }
    
    /**
     * 发送GET请求
     */
    public HttpResponse sendGet(String url, Map<String, Object> params) {
        SignParams signParams = generateSignParams();
        params.put(SignConstants.HEADER_TIMESTAMP, signParams.getTimestamp());
        params.put(SignConstants.HEADER_NONCE, signParams.getNonce());
        
        String signature = generateSign(params);
        
        HttpRequest request = HttpRequest.get(url)
                .header(SignConstants.HEADER_APP_ID, appId)
                .header(SignConstants.HEADER_TIMESTAMP, String.valueOf(signParams.getTimestamp()))
                .header(SignConstants.HEADER_NONCE, signParams.getNonce())
                .header(SignConstants.HEADER_SIGNATURE, signature);
        
        // 添加查询参数
        if (params != null && !params.isEmpty()) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                if (!isSignHeader(entry.getKey())) {
                    request.form(entry.getKey(), entry.getValue());
                }
            }
        }
        
        if (debugMode) {
            log.info("发送GET请求: {}", request.getUrl());
        }
        
        return request.execute();
    }
    
    /**
     * 发送POST请求
     */
    public HttpResponse sendPost(String url, Map<String, Object> params, String body) {
        SignParams signParams = generateSignParams();
        params.put(SignConstants.HEADER_TIMESTAMP, signParams.getTimestamp());
        params.put(SignConstants.HEADER_NONCE, signParams.getNonce());
        
        String signature = generateSignWithBody(params, body);
        
        HttpRequest request = HttpRequest.post(url)
                .header(SignConstants.HEADER_APP_ID, appId)
                .header(SignConstants.HEADER_TIMESTAMP, String.valueOf(signParams.getTimestamp()))
                .header(SignConstants.HEADER_NONCE, signParams.getNonce())
                .header(SignConstants.HEADER_SIGNATURE, signature)
                .contentType("application/json");
        
        // 添加表单参数
        if (params != null && !params.isEmpty()) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                if (!isSignHeader(entry.getKey())) {
                    request.form(entry.getKey(), entry.getValue());
                }
            }
        }
        
        // 设置请求体
        if (StrUtil.isNotBlank(body)) {
            request.body(body);
        }
        
        if (debugMode) {
            log.info("发送POST请求: {}", request.getUrl());
            log.info("请求体: {}", body);
        }
        
        return request.execute();
    }
    
    /**
     * 发送POST请求（JSON）
     */
    public HttpResponse sendPostJson(String url, Object data) {
        String jsonBody = JSONUtil.toJsonStr(data);
        return sendPost(url, new TreeMap<>(), jsonBody);
    }
    
    /**
     * 验证时间戳
     */
    public boolean isValidTimestamp(long timestamp) {
        long disparity = Math.abs(System.currentTimeMillis() - timestamp);
        return disparity <= timestampDisparity;
    }
    
    /**
     * 判断是否为签名头
     */
    private boolean isSignHeader(String key) {
        return SignConstants.HEADER_APP_ID.equals(key) ||
               SignConstants.HEADER_TIMESTAMP.equals(key) ||
               SignConstants.HEADER_NONCE.equals(key) ||
               SignConstants.HEADER_SIGNATURE.equals(key) ||
               SignConstants.HEADER_HASHED_REQUEST_PAYLOAD.equals(key);
    }
    
    /**
     * 签名参数类
     */
    @Data
    public static class SignParams {
        private String appId;
        private long timestamp;
        private String nonce;
        
        public SignParams(String appId, long timestamp, String nonce) {
            this.appId = appId;
            this.timestamp = timestamp;
            this.nonce = nonce;
        }
    }
}